/**
 * @copyright Copyright (c) 2023 Xutao Wang, All Rights Reserved.
 * @file      lite_button.c
 * @brief     LiteButton operating module that using reading level methon by gpio in the service layer.
 * @version   Initial revision. Rev 1.0.0
 * @author    Wang Xutao (Xutao_Wang1993@163.com)
 * @date      2023-06-22
 * ********************************************************************************
 * @par       history (The following format is used to generate help documents.)
 * <table>
 * <tr><th>Version <th>Date    <th>Author      <th>Changes and comments
 * <tr><td>V1.0.0 <td>2023-06-22  <td>Wang Xutao    <td>Created
 * </table>
 */

/**************************************** Includes  ****************************************/
#include "lite_button.h"
#include "lite_button_cfg.h"

/**************************************** Definitions  ****************************************/

/* The header pointer of the blinking link-list. */
static xLiteLinkedList s_LinkedList = {
    .Head = NULL,
    .Length = 0,
};

/**************************************** Functions  ****************************************/

/**
 * @brief     Check the validation of the button.
 * @param     Button   The button strcut.
 * @return    uint8_t  The result of the operations. 0 means no errors.
 */
static uint8_t LiteButton_Check(xLiteButton *Button)
{
    if (Button == NULL)
    {
        return true;
    }
    return false;
}

/**
 * @brief     Button driver core function, driver state machine.
 * @param     Button   The button strcut.
 */
static void LiteButton_Handler(xLiteButton *Button)
{
    /* State enum */
    typedef enum
    {
        eState_Reset = 0,
        eState_Released,
        eState_Pressed,
        eState_Hold,
        // eState_Idle,
    } eState;
    /*
     * Notes:
     * 1.Get the current pin level.
     * 2.Ticks++.
     * 3.Debounce.
     * 4.State machine to confirm key state.
     */
    if (LiteButton_Check(Button))
    {
        return;
    }
    if (Button->GetPinState == NULL)
    {
        return;
    }
    /* Get the pin state. */
    uint8_t CurrentPinLevel = (Button->GetPinState(Button->ID) == 0) ? (0) : (1);

    /* Ticks counter is working. */
    Button->Ticks++;

    /* Debounce. */
    if (CurrentPinLevel != Button->ButtonLevel) /* Current state is not equal to previous one. */
    {
        /* If the consecutive getting pin level times exceed the debounce ticks, a new level state has been entered. */
        Button->DebounceCount++;
        if (Button->DebounceCount >= LITE_BUTTON_DEBOUNCE_TICKS)
        {
            Button->ButtonLevel = CurrentPinLevel;
            Button->DebounceCount = 0;
        }
    }
    else
    {
        /* Leved is not changed ,and debounce counter needs to be reset. */
        Button->DebounceCount = 0;
    }

    /* State machine */
    switch (Button->State)
    {
    case eState_Reset:
        Button->Event = (uint8_t)eLiteButtonEvent_None;
        Button->Repeat = 0;
        Button->Ticks = 0;
        Button->State = eState_Released;
        break;
    case eState_Released:
        /* Start to press. */
        if (Button->ButtonLevel == Button->ActiveLevel)
        {
            /* Start pressing down for the first time. */
            Button->Event = (uint8_t)eLiteButtonEvent_PressDown;
            /* Start timing at first press. */
            if (Button->Repeat == 0)
            {
                Button->Ticks = 0;
            }
            Button->Repeat++;
            Button->State = eState_Pressed;
            if (Button->Callback[Button->Event] != NULL)
            {
                Button->Callback[Button->Event]((uint8_t)Button->ID, (uint8_t)Button->Event);
            }
        }
        /* Release from press. */
        else if (Button->Repeat == 0)
        {
            Button->Event = (uint8_t)eLiteButtonEvent_None;
            break;
        }
        else
        {
            /* Continue click ticks timeout. */
            if (Button->Ticks > LITE_BUTTON_CONTINIOUS_CLICK_TIMEOUT)
            {
                switch (Button->Repeat)
                {
                case 1:
                    Button->Event = (uint8_t)eLiteButtonEvent_Click;
                    break;
                case 2:
                    Button->Event = (uint8_t)eLiteButtonEvent_DoubleClick;
                    break;
                default:
                    Button->Event = (uint8_t)eLiteButtonEvent_MultiClick;
                    break;
                }
                if (Button->Callback[Button->Event])
                {
                    Button->Callback[Button->Event]((uint8_t)Button->ID, (uint8_t)Button->Event);
                }
                Button->State = eState_Reset;
            }
        }
        break;
    case eState_Pressed:
        /* Release from press. */
        if (Button->ButtonLevel != Button->ActiveLevel)
        {
            Button->Event = (uint8_t)eLiteButtonEvent_PressUp;
            if (Button->Callback[Button->Event] != NULL)
            {
                Button->Callback[Button->Event]((uint8_t)Button->ID, (uint8_t)Button->Event);
            }
            Button->State = eState_Released;
        }
        /* Detect button hold. */
        else if (Button->Ticks > LITE_BUTTON_HOLD_TIMEOUT_TICKS)
        {
            Button->Event = (uint8_t)eLiteButtonEvent_HoldStart;
            if (Button->Callback[Button->Event])
            {
                Button->Callback[Button->Event]((uint8_t)Button->ID, (uint8_t)Button->Event);
            }
            Button->State = eState_Hold;
        }
        break;
    case eState_Hold:
        /* LiteButton holding. */
        if (Button->ButtonLevel == Button->ActiveLevel)
        {
            /* Continue hold trigger */
            if (Button->Ticks % LITE_BUTTON_HOLD_INTERVAL_TICKS == 0)
            {
                Button->Event = (uint8_t)eLiteButtonEvent_Holding;
                if (Button->Callback[Button->Event])
                {
                    Button->Callback[Button->Event]((uint8_t)Button->ID, (uint8_t)Button->Event);
                }
            }
        }
        /* Releasd from the hold state. */
        else
        {
            Button->Event = (uint8_t)eLiteButtonEvent_HoldStop;
            if (Button->Callback[Button->Event])
            {
                Button->Callback[Button->Event]((uint8_t)Button->ID, (uint8_t)Button->Event);
            }
            Button->State = eState_Reset;
        }
        break;
    default:
        Button->State = eState_Released;
        break;
    }
}

/**************************************** Interfaces  ****************************************/

/**
 * @brief     Initialize the button struct.
 * @param     Button   The struct of button.
 * @param     ID       The button id.
 * @param     GetPinState Read the button level of gpio pin.
 * @param     ActiveLevel Pressed pin level.
 * @param     HoldTimeout Holding trigger timeout.0 is default.
 * @return    uint8_t  The result of the operations. 0 means no errors.
 */
uint8_t Lite_Button_Init(xLiteButton *Button, uint32_t ID, uint8_t (*GetPinState)(uint32_t), uint8_t ActiveLevel, uint32_t HoldTimeout)
{
    if (LiteButton_Check(Button) != false)
    {
        return true;
    }
    if (GetPinState == NULL)
    {
        return true;
    }
    Button->Ticks = 0;
    Button->ID = ID;
    Button->Repeat = 0;
    Button->Event = 0;
    Button->State = 0;
    Button->DebounceCount = 0;
    Button->ActiveLevel = (ActiveLevel == 0) ? (0) : (1);
    Button->HoldTimeout = (HoldTimeout < LITE_BUTTON_HOLD_TIMEOUT_TICKS) ? (LITE_BUTTON_HOLD_TIMEOUT_TICKS) : (HoldTimeout);
    Button->GetPinState = GetPinState;
    Button->ButtonLevel = (Button->GetPinState(Button->ID) == 0) ? (0) : (1); /* Default button pin level. */
    Button->Event = (uint8_t)eLiteButtonEvent_None;
    for (uint8_t i = 0; i < eLiteButtonEvent_Max; i++)
    {
        Button->Callback[i] = NULL;
    }
    return false;
}

/**
 * @brief     Attach the button event callback function.
 * @param     Button   The struct of button.
 * @param     Event    Trigger event type.
 * @param     Callback Callback function of triggered event.
 * @return    uint8_t  The result of the operations. 0 means no errors.
 */
uint8_t Lite_Button_Attach(xLiteButton *Button, eLiteButtonEvent Event, void (*Callback)(uint8_t ID, uint8_t Event))
{
    if (LiteButton_Check(Button))
    {
        return true;
    }
    if (Callback == NULL)
    {
        return true;
    }
    Button->Callback[Event] = Callback;
    return false;
}

/**
 * @brief     Inquire the button event happen.
 * @param     Button   The struct of button.
 * @param     eLiteButtonEvent Current button event.
 * @return    uint8_t  The result of the operations. 0 means no errors.
 */
uint8_t Lite_Button_GetEvent(xLiteButton *Button, eLiteButtonEvent *Event)
{
    if (LiteButton_Check(Button))
    {
        *Event = eLiteButtonEvent_None;
        return true;
    }
    *Event = (eLiteButtonEvent)(Button->Event);
    return false;
}

/**
 * @brief     Start the button work, add the button into work list.
 * @param     Button   The target button strcut.
 * @return    uint8_t  The result of the operations. 0 means no errors.
 */
uint8_t Lite_Button_Start(xLiteButton *Button)
{
    if (LiteButton_Check(Button))
    {
        return true;
    }
    Button->Node.Data = Button;
    Lite_LinkedList_Add(&s_LinkedList, &Button->Node);
    return false;
}

/**
 * @brief     Stop the button work, remove the button off work list.
 * @param     Button   The target button strcut.
 * @return    uint8_t  The result of the operations. 0 means no errors.
 */
uint8_t Lite_Button_Stop(xLiteButton *Button)
{
    if (LiteButton_Check(Button))
    {
        return true;
    }
    Lite_LinkedList_Delete(&s_LinkedList, &Button->Node);
    return true;
}

/**
 * @brief     The tick function.
 */
void Lite_Button_Tick(void)
{
    uint32_t Length = 0;
    if (Lite_LinkedList_GetLength(&s_LinkedList, &Length))
    {
        return;
    }
    if (Length == 0)
    {
        return;
    }
    xLiteLinkedListNode Node;
    xLiteButton *Target;
    for (uint16_t i = 0; i < Length; i++)
    {
        if (Lite_LinkedList_Get(&s_LinkedList, &Node, i))
        {
            return;
        }
        Target = (xLiteButton *)(Node.Data);
        LiteButton_Handler(Target);
    }
}
